using System;
using System.Reflection;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace remjobnet
{
    public enum Command :ushort 
    {
        DISCOVERY,
        HELLO,
        START,
        STOP,
        INSTALL,
        ACK,
        NACK,
        FILEM,
        UNINSTALL,
        LOG,
        LOGREPLY,
        STATUS_REQ,
        STATUS_REP,
    };

    public class PacketUtility
    {
        public const int MAXPACKETSIZE = 1500;
        public const int segmentLength = 1024;

        static public FieldInfo[] getFields(Type t){
            FieldInfo[] f = t.GetFields();
            return f;
        }
    }

    public class PacketSerializer
    {
        private Packet p;
        public byte[] res;
        public int len;

        public PacketSerializer(Packet p)
        {
            this.p = p;
            res = new byte[PacketUtility.MAXPACKETSIZE];
        }

        public void writeField(FieldInfo f)
        {
            Type t = f.FieldType;

            if (t == typeof(Command) || t == typeof(ushort))
            {
                writeValue((ushort)f.GetValue(p));
            }
            else if (t == typeof(uint))
            {
                writeValue((uint)f.GetValue(p));
            }
            else if (t == typeof(string))
            {
                writeValue((String)f.GetValue(p));
            }
            else if (t == typeof(List<ushort>))
            {
                writeValue((List<ushort>)f.GetValue(p));
            }
            else if (t == typeof(byte[]))
            {
                writeValue((byte[])f.GetValue(p));
            }
            else if (t== typeof(long))
            {
                writeValue((long)f.GetValue(p));
            }
            else
            {
                Console.WriteLine("Errore");
            }
            
        }

        public void writeValue(byte[] v)
        {
            uint slen = (uint)v.Length;
            byte[] b = BitConverter.GetBytes(slen);
            Buffer.BlockCopy(b, 0, res, len, b.Length);
            len += b.Length;

            Buffer.BlockCopy(v, 0, res, len, v.Length);
            len += v.Length;
        }

        public void writeValue(UInt16 v)
        {
            byte[] b = BitConverter.GetBytes(v);
            Buffer.BlockCopy(b,0,res,len,b.Length);
            len += b.Length;
        }

        private void writeValue(UInt32 v)
        {
            byte[] b = BitConverter.GetBytes(v);
            Buffer.BlockCopy(b, 0, res, len, b.Length);
            len += b.Length;
        }

        private void writeValue(long v)
        {
            byte[] b = BitConverter.GetBytes(v);
            Buffer.BlockCopy(b, 0, res, len, b.Length);
            len += b.Length;
        }

        private void writeValue(String v)
        {
            byte[] b = Encoding.ASCII.GetBytes(v);
            ushort slen = (ushort) b.Length;
            writeValue(slen);
            
            Buffer.BlockCopy(b, 0, res, len, slen);
            len += slen;
        }

        private void writeValue(List<ushort> lv)
        {
            if (lv != null)
            {
                ushort count = (ushort)lv.Count;
                writeValue(count);
                lv.ForEach(new Action<ushort>(this.writeValue));
            }
            else
                writeValue(0);
        }

        public void serialize()
        {
            
            len = 0;

            Type t = p.GetType();
            FieldInfo[] fi = PacketUtility.getFields(t);
            Array.Reverse(fi);
            Array.ForEach<FieldInfo>(fi, new Action<FieldInfo>(this.writeField));    
        }
    }

    public class PacketUnSerializer
    {
        private int len;
        private Reader r;
        private Packet pa;

        private interface Reader
        { 
            byte[] read(int n);
        }

        private class NetworkReader : Reader
        {
            private NetworkStream ns;

            public NetworkReader(NetworkStream ns)
            {
                this.ns = ns;
            }

            public byte[] read(int n)
            {
                byte[] res = new byte[n];
                ns.Read(res,0,n);
                return res;
            }
        }

        private class ByteReader : Reader
        {
            private byte[] b;
            private int cur;

            public ByteReader(byte[] buffer)
            {
                this.b = buffer;
                this.cur = 0;
            }

            public byte[] read(int n)
            {
                byte[] res = new byte[n];
                Buffer.BlockCopy(b, cur, res, 0, n);
                //Array.ForEach<byte>(res, delegate(byte b1) { Console.WriteLine(b1.ToString()); });
                cur += n;
                return res;
            }

        }

        public PacketUnSerializer(Byte[] p, int len)
        {
            this.len = len;
            r = new ByteReader(p);
        }

        public PacketUnSerializer(NetworkStream s)
        {
            r = new NetworkReader(s);
        }

        public void readField(FieldInfo f)
        {
            Type t = f.FieldType;
            //Console.WriteLine("leggo " + f.Name);

            if (t == typeof(Command) || t == typeof(ushort))
            {
                readShort(f);
            }
            else if (t == typeof(uint))
            {
                readInt(f);
            }
            else if (t == typeof(long))
            {
                readLong(f);
            }
            else if (t == typeof(string))
            {
                readString(f);
            }
            else if (t == typeof(List<ushort>))
            {
                readList(f);
            }
            else if (t == typeof(byte[]))
            {
                readBuffer(f);
            }
            else
            {
                Console.WriteLine("Errore");
            }
        }

        public void readBuffer(FieldInfo f)
        {
            byte[] buffer = r.read(4);
            uint slen = BitConverter.ToUInt32(buffer, 0);

            buffer = r.read((int)slen);
            f.SetValue(pa, buffer);
        }

        public void readShort(FieldInfo f)
        {
            byte[] buffer = r.read(2);
            ushort s = BitConverter.ToUInt16(buffer, 0);

            f.SetValue(pa, s);
        }

        private void readInt(FieldInfo f)
        {
            byte[] buffer = r.read(4);
            uint s = BitConverter.ToUInt32(buffer, 0);
            f.SetValue(pa, s);
        }

        private void readLong(FieldInfo f)
        {
            byte[] buffer = r.read(sizeof(long));
            uint s = BitConverter.ToUInt32(buffer, 0);
            f.SetValue(pa, s);
        }

        private void readString(FieldInfo f)
        {
            byte[] buffer = r.read(2);
            ushort slen = BitConverter.ToUInt16(buffer, 0);
 
            buffer = r.read((int)slen);
            string s = Encoding.ASCII.GetString(buffer,0,(int)slen);
            f.SetValue(pa,s);
        }

        private void readList(FieldInfo f)
        {
            byte[] buffer = r.read(2);
            ushort count = BitConverter.ToUInt16(buffer, 0);
            //Console.WriteLine(count);

            List<ushort> list = null;
            if (count > 0)
            {
                list = new List<ushort>();
                ushort g;

                for (int i = 0; i < count; i++)
                {
                    buffer = r.read(2);
                    g = BitConverter.ToUInt16(buffer, 0);
                    //Console.WriteLine(g);
                    list.Add(g);
                }
            }
            
            f.SetValue(pa, list);
        }

        public Packet unserialize()
        {
            byte[] b = r.read(2);
            Command c = (Command) BitConverter.ToInt16(b,0);
                                  
            switch (c){
                //Packet
                case Command.HELLO:
                case Command.DISCOVERY:
                case Command.STATUS_REQ:
                case Command.LOGREPLY:
                case Command.ACK:
                    pa = new Packet();
                    break;

                //PApplication
                case Command.STOP:
                case Command.UNINSTALL:
                case Command.LOG:
                    pa = new PApplication();
                    break;

                //PFile
                case Command.INSTALL:
                case Command.START:
                    pa = new PFile();
                    break;
                
                //PFiletransfer
                case Command.FILEM:
                    pa = new PFileTransfer();
                    break;

                //PNack
                case Command.NACK:
                    pa = new PNack();
                    break;

                case Command.STATUS_REP:
                    pa = new PStatus();
                    break;
            }

            Type t = pa.GetType();
            FieldInfo[] fi = PacketUtility.getFields(t);
            Array.Reverse(fi);
            //type
            fi[0].SetValue(pa, c);
            for (int i = 1; i < fi.Length; i++)
            {
                readField(fi[i]);
            }

            return pa;
        }
    }

    public class Packet
    {
        public List<ushort> id_groups;
        public uint id_sender;
        public ushort id_operation;
        public Command type;

        public Packet(Command type, ushort id_operation, uint id_sender, List<ushort> id_groups)
        {
            this.type = type;
            this.id_operation = id_operation;
            this.id_sender = id_sender;
            this.id_groups = id_groups;
        }

        public Packet() { 
        }
    }

    public class PFile : Packet
    {
        //byte[] checksum;
        public ushort id_file_operation;
        public long len;
        public string application;
        public ushort size;
        public ushort id_application;

        public PFile(
            Command type,
            ushort id_operation,
            uint id_sender,
            List<ushort> id_groups,
            ushort id_application,
            ushort size,
            string application,
            long len,
            ushort id_file_operation)
            : base(type, id_operation, id_sender, id_groups)
        {
            this.id_application = id_application;
            this.size = size;
            this.application = application;
            this.len = len;
            this.id_file_operation = id_file_operation;
        }

        public PFile() { }
    }

    public class PApplication : Packet
    {
        public string application;
        public ushort id_application;

        public PApplication(
            Command type,
            ushort id_operation,
            uint id_sender,
            List<ushort> id_groups,
            ushort id_application,
            string application)
            : base(type, id_operation, id_sender, id_groups)
        {
            this.id_application = id_application;
            this.application = application;
        }

        public PApplication() { }
    }


    public class PFileTransfer : Packet
    {
        public ushort len;
        public byte[] buffer;
        public uint offset;
        public ushort id_application;
        
        public PFileTransfer(
            Command type,
            ushort id_operation,
            uint id_sender,
            List<ushort> id_groups,
            ushort id_application,
            uint offset,
            byte[] buffer,
            ushort len)
            : base(type, id_operation, id_sender, id_groups)
        {
            this.id_application = id_application;
            this.offset = offset;
            this.buffer = buffer;
            this.len = len;
        }

        public PFileTransfer() { }
    }

    public class PNack : Packet
    {
        public uint offset;

        public PNack(
            Command type,
            ushort id_operation,
            uint id_sender,
            List<ushort> id_groups,
            uint offset)
            : base(type, id_operation, id_sender, id_groups)
        {
            this.offset = offset;
        }

        public PNack() { }
    }

    public class PStatus : Packet
    {
        public List<ushort> statusList;

        public PStatus(
            Command type,
            ushort id_operation,
            uint id_sender,
            List<ushort> id_groups,
            List<ushort> status)
            : base(type, id_operation, id_sender, id_groups)
        {
            this.statusList = status;
        }

        public PStatus() { }
    }
}
